/* hexedit -- Hexadecimal Editor for Binary Files
   Copyright (C) 1998 Pixel (Pascal Rigaux)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/

#include <assert.h>

#include "crule.h"
#include "dhexview.h"

int move_cursor(INT delta)
{
    return set_cursor(base + cursor + delta);
}

int set_cursor(INT loc)
{
    if (loc < 0 && base % lineLength)
        loc = 0;

    if (!tryloc(loc))
        return false;

    if (loc < base) {
        if (loc - base % lineLength < 0)
            set_base(0);
        else if (!move_base
                 (myfloor(loc - base % lineLength, lineLength) +
                  base % lineLength - base))
            return false;
        cursor = loc - base;
    } else if (loc >= base + page) {
        if (!move_base
            (myfloor(loc - base % lineLength, lineLength) +
             base % lineLength - page + lineLength - base))
            return false;
        cursor = loc - base;
    } else if (loc > base + nbBytes) {
        return false;
    } else
        cursor = loc - base;

    if (mark_set)
        updateMarked();

    position_history->push(position_history, loc);

    return true;
}

int move_base(INT delta)
{
    return set_base(base + delta);
}

int set_base(INT loc)
{
    if (loc < 0)
        loc = 0;

    if (!tryloc(loc))
        return false;
    base = loc;
    readFile();

    if (nbBytes < page - lineLength && base != 0) {
        base -= myfloor(page - nbBytes - lineLength, lineLength);
        if (base < 0)
            base = 0;
        readFile();
    }

    if (cursor > nbBytes)
        cursor = nbBytes;
    return true;
}


int computeLineSize(void)
{
    return computeCursorXPos(lineLength - 1, 0) + 1;
}
int computeCursorXCurrentPos(void)
{
    return computeCursorXPos(cursor, hexOrAscii);
}
int computeCursorXPos(int cursor, int hexOrAscii)
{
    int r = 11;
    int x = cursor % lineLength;
    int h = (hexOrAscii ? x : lineLength - 1);

    r += normalSpaces * (h % blocSize) +
        (h / blocSize) * (normalSpaces * blocSize + 1) + (hexOrAscii
                                                          && cursorOffset);

    if (!hexOrAscii)
        r += x + normalSpaces + 1;

    return r;
}


#ifdef HAVE_COLORS
static int *color_buffer;   // This stores COLOR_PAIR values.
#endif

/*******************************************************************************/
/* Curses functions */
/*******************************************************************************/
void initCurses(void)
{
    initscr();

#ifdef HAVE_COLORS
    start_color();
    use_default_colors();
#endif

    refresh();
    raw();
    noecho();
    keypad(stdscr, true);

    if (LINES <= 4)
        DIE("%s: term is too small (height)\n");

    blocSize = 4;
    for (lineLength = blocSize; computeLineSize() <= COLS;
         lineLength += blocSize);
    lineLength -= blocSize;
    if (lineLength == 0)
        DIE("%s: term is too small (width)\n");

    page = lineLength * (LINES - 1);

    colsUsed = computeLineSize();
    buffer = malloc(page);
    color_buffer = calloc(page, sizeof(int));
    bufferAttr = malloc(page * sizeof(*bufferAttr));
}

void exitCurses(void)
{
    close(fd);
    clear();
    refresh();
    endwin();
#ifdef HAVE_COLORS
    crulist->destroy(crulist);
    free(color_buffer);
#endif
}

static void
displayRegularModeline (void)
{
    printw("---  %s       -- 0x%llX ", baseName, base + cursor);
    if (MAX(fileSize, lastEditedLoc))
        printw("/ 0x%llX ", getfilesize());
    return;
}

static void
displayAlternativeModeline (void)
{
    union {
        uint32_t u4;
        uint16_t u2;
        uint8_t  u1;
        char     c[4];
    } u;
    u.u4 = 0;
    LSEEK(fd, cursor + base); 
    read(fd, &u, 4);

    printw("--- ");

#define BITSET(_x_, _b_) ((_x_ & (1 << _b_)) ? 1 : 0)
    int i;
    for (i = 7; i >= 0; --i)
        printw((4 == i ? "%d " : "%d"), BITSET(u.u1, i));

    printw(" --%*d", 4, u.u1);
    printw(" --%*d", 6, u.u2);
    printw(" --%*u", 11, u.u4);
#define PR(_c_) (isprint(_c_) ? _c_ : '.')
    printw(" -- %c%c%c%c", PR(u.c[0]), PR(u.c[1]), PR(u.c[2]),
                           PR(u.c[3]));
    printw(" ");

    return;
}

static inline void
displayModeline (void)
{
    if (dhexview_options.alternate_toolbar) {
        displayAlternativeModeline();
    } else {
        displayRegularModeline();
    }

    return;
}

void display(void)
{
    if (colored) {
        memset(color_buffer, 0, sizeof(int) * nbBytes);
        crulist->colorize(crulist, base, buffer, nbBytes, color_buffer);
        if (dhexview_options.hlsearch) {
            hlsearch_crulist->colorize(hlsearch_crulist, base, buffer,
                                       nbBytes, color_buffer);
        }
    }

    int i;
    for (i = 0; i < nbBytes; i += lineLength) {
        move(i / lineLength, 0);
        displayLine(i, nbBytes);
    }

    for (; i < page; i += lineLength) {
        int j;
        move(i / lineLength, 0);
        for (j = 0; j < colsUsed; j++)
            printw(" ");        /* cleanup the line */
        move(i / lineLength, 0);
        PRINTW(((fileSize & 0xFFFFFFFF00000000uL) ?  "%09lX" : "%08lX",
                (base + i)));
    }

    attrset(NORMAL);
    move(LINES - 1, 0);
    for (i = 0; i < colsUsed; i++)
        printw("-");
    move(LINES - 1, 0);

    displayModeline();

    move(cursor / lineLength, computeCursorXCurrentPos());
}

void displayLine(int offset, int max)
{
    int i;

    PRINTW(((fileSize & 0xFFFFFFFF00000000uL ? "%09lX  " : "%08lX   "),
                (base + offset)));
    for (i = offset; i < offset + lineLength; i++) {
        if (i > offset) {
            MAXATTRPRINTW(
                ((IS_LEFT_PANE_COLORED() && dhexview_options.color_between_bytes ?
                  color_buffer[i] : 0) |
                (bufferAttr[i] & MARKED)
                ),
                          (((i - offset) % blocSize) ? " " : "  "));
        }

        if (i < max) {
            ATTRPRINTW(
                          (IS_LEFT_PANE_COLORED() ? color_buffer[i] : 0) |
                          bufferAttr[i], ("%02X", buffer[i]));
        } else
            PRINTW(("  "));
    }
    PRINTW(("  "));
    for (i = offset; i < offset + lineLength; i++) {
        if (i >= max)
            PRINTW((" "));
        else if (buffer[i] >= ' ' && buffer[i] < 127)
            ATTRPRINTW(
                (IS_RIGHT_PANE_COLORED() ? color_buffer[i] : 0)
                    | bufferAttr[i], ("%c", buffer[i]));
        else
            ATTRPRINTW((IS_RIGHT_PANE_COLORED() ? color_buffer[i] : 0)
                    | bufferAttr[i], ("."));
    }
}

void clr_line(int line)
{
    move(line, 0);
    clrtoeol();
}

void displayCentered(char *msg, int line)
{
    clr_line(line);
    move(line, (COLS - strlen(msg)) / 2);
    PRINTW(("%s", msg));
}

void displayOneLineMessage(char *msg)
{
    int center = page / lineLength / 2;
    clr_line(center - 1);
    clr_line(center + 1);
    displayCentered(msg, center);
}

void displayTwoLineMessage(char *msg1, char *msg2)
{
    int center = page / lineLength / 2;
    clr_line(center - 2);
    clr_line(center + 1);
    displayCentered(msg1, center - 1);
    displayCentered(msg2, center);
}

void displayMessageAndWaitForKey(char *msg)
{
    displayTwoLineMessage(msg, pressAnyKey);
    getch();
}

int displayMessageAndGetString(char *msg, char **last, char *p, int p_size)
{
    int ret = true;

    displayOneLineMessage(msg);
    ungetstr(*last);
    echo();
    getnstr(p, p_size - 1);
    noecho();
    if (*p == '\0') {
        if (*last)
            strcpy(p, *last);
        else
            ret = false;
    } else {
        FREE(*last);
        *last = strdup(p);
    }
    return ret;
}

void ungetstr(char *s)
{
    char *p;
    if (s)
        for (p = s + strlen(s) - 1; p >= s; p--)
            ungetch(*p);
}

int get_number(INT * i)
{
    INT current_location = cursor + base;
    int err;
    int oper = 0;
    char tmp[BLOCK_SEARCH_SIZE];
    char *ptr = tmp;
    echo();
    getnstr(tmp, BLOCK_SEARCH_SIZE - 1);
    noecho();

    /* Number can be prepended with optional plus or minus, in that case
     * move that many bytes in either direction.
     */
    if ('-' == tmp[0]) {
        oper = 1;
        ++ptr;
    } else if ('+' == tmp[0]) {
        oper = 2;
        ++ptr;
    }

    if (strbeginswith(ptr, "0x"))
        err = sscanf(ptr + strlen("0x"), "%lx", i);
    else
        err = sscanf(ptr, "%ld", i);

    if (1 == oper)
        *i = current_location - *i;
    else if (2 == oper)
        *i = current_location + *i;

    return err == 1;
}
